<?php

namespace Org\Su;

/**
 * SuEncrypt 实用加密类
 * 用途：数组转序列号,字符串加密,支持中文
 * 吐槽：PHP加密功能的完成总算解决了自己的一个心病。
 *  以前somda挺强大的，但是逻辑想法太复杂，原来的草稿又遗失了，现在想看代码回忆流程有点头疼。
 *  这个加密版本比较简洁，性能也还不错，速度方面理论上应该比somda要高，虽然强度没有somda复杂，
 *  但是满足生产环境的需求应该是绰绰有余了，关键是要把本文件加密，不然被下载了就容易破解了。
 * @since 1.0 <2015-7-9> SoChishun Added.
 */
class SuEncrypt {

    // ==字符串加密==
    /**
     * 混淆字典
     * @var array
     * @since 1.0 <2015-7-9> SoChishun Added.
     */
    protected $str_dict = array(
        'QFLUWrsvzf#T7xjum6ly2.bkAtSBGqgEDn5oICd*N4aHiROMwYV8J%eX3KPh9cZ10p',
        '%rXuJ*e#i9Mp2tZs4Nw8fy0hdEWlaFjQTgA.vkUVxK6mqBDI1HG7O5LoSbnY3CRczP',
        'LWsk2yhdt*Cxv5H3ZuRS#lK6zOgUGcMPqYNI4jQimfaDE.0nFBTX7eoVpr1Abw9%8J',
        'MonD#NBSmXid*12Qvc9CAOjb5qTyJHf4IL7xFVsPKGU0ktR%glze6YW8hZ.pw3ruaE',
        '9SgQzxHVy56Jboj4GiEF.UD%*rfuqpP0OR1hsBtZXdkcYIn2eT3AL#lC7MwamNK8Wv',
        '%5cwubKe0t2WXsA*ENB9JVOY4Gq67jLQfalrCn3pHI#dogvyDRPm1M8SUihFZzxTk.',
        'agjQ0Gzq7*x9oYp6BS5VWRsmDdJZUXF.Ovc#8I%f2rLyikTe1w4CtP3EHhuMNKblAn',
        'AgqY95yjbv2IMfn*swdhxoUic0kOJpF7S%3z6XHLGBVEZaWtT8KPrCR4le#N1D.Qmu',
        'GhsXRTk6nJ9c3QPf*pK1#tMyE4zgwoW5I2.B%AbqmCDrUZYl0HuOi7SxedjVL8vNFa',
        '9bBdNLqRm#YAoUIh1OZnspczEX7WQwe82y3g*.G4H%xTjM5PlCD0Ffv6SkVrutKaJi',
        'TmxEoMCfe8y4dAVP1ROlh#0c.jw%i7uXQInUq9ZFbJg6Kra*kGsvY5BHND3Lztp2WS',
        'xopdIXB98kTAn.j5Nfb%YR4tgai2ch*PuU0KHyrq#CVlezZWF61QvJEMO3Ssm7LGDw',
        'nryjbPJqDCtZQAsFwWNG7Ucev16.K8%hf5oX2xgVma0kuY4l9M*#LpBdEzRiTS3IOH',
        'h9PlIS7Vexu3UJfgNaFdXkzLHTEqpbCGvB8wnAZRci0KD#.jr5t4YosO2y6*Wm1M%Q',
        '4svEum9WXIq%ZpHt2KQS06gbR#AD81OF5BVkfahUj3zdnGPwJMCNyc.o7Ll*TxiYer',
        'iSzogqXRycxd7K0%ME3Y1NBIl9kTW*UDrQZG5a86Abhujw2LtpvOJCFes.fm4P#HnV',
        'JyMAxqsNIm2YTe#a3Xjg1KVbwGlSHPiF46pU0R*8LhCcnEf5dWBQruzk%97OvoD.Zt',
        'mxIKSiDHyzERYVleu*AGPjhC4fU.2JFkt%pocON#Z0qaTdM9Qrbw1W3Lgv85s6nXB7',
        'xZODUrNJ*0S6qQz4Yl8gsi%ChPKtEBb9FwnLjXW1pmadu27y5fHTGkIVec#ovA3MR.',
        'FlIbiRJn*LgYxuEszvk5P3ZSH#QTwy10Nat.WA7hD2VBCOfUcqp%oe4mKd98rj6GMX',
        'fI*em41Yc58W23O7sZ.vPKti#rBjwNabMqDhXzRxGd0LCSEJAoUk%FyQVuH9nlgpT6',
        'UoFS0hVO32Diuyxml8awLdHbqATz#rPfNvZ7JpI4Yg.MK6*WEX%cenGQBjR5t9kCs1',
        'ZicP5xpvmO67jCDVbqXtT*E9.zB0JgY%WIN3yAheUnd2QLSMkrR1#KG4Hsuflowa8F',
        'rQbmVTZestvclIgYW4SjKhODPapRGx*.yE79uHFN6JAL3C#zB2U0dqoXw5nfk%iM81',
        'gbInoyD7kpZz6cQB.jNsSWqXK9xEG2CweYLOa3iR%Mf0#u5Jd14VhAv8lt*PHrmUFT',
        'ygEVcZvmoWbRIOK%5tA4MBGa36sHCL*hX9lP7S2wj8JDeFU0NxfT.dpY1i#qQzkunr',
        'q5o4euyjflNTQDix7d#R0atsBYPZAGLVE*nzJvm8XHKwC9M2bkg%IO3.FcUpWS1h6r',
    );

    /**
     * 字符串混淆
     * 只支持英文和数字,不支持中文
     * @param string $str 原文
     * @return string 混淆后的字符串, 首字符是大小写字母
     * @since 1.0 <2015-7-9> SoChishun Added.
     */
    public function str_mix($str) {
        if (!$str) {
            return $str;
        }
        $dict_idx = 'hUBQDoaSmipMGgtfqvFZsCJreVKxLPAjTbkOWcnwYdzXyRlNIuEH';
        $dict_org = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+=/-';
        $id = rand(0, 26);
        $dictmix = $this->str_dict[$id]; // 随机抽取一组混淆码
        $idx = $dict_idx[$id];
        $n = strlen($str) - 1;
        $mix = '';
        while ($n >= 0) {
            $i = strpos($dict_org, $str[$n]);
            $mix.=$dictmix[$i];
            $n--;
        }
        return $idx . $mix;
    }

    /**
     * 反字符串混淆
     * @param string $str 混淆后的字符串
     * @return string 未混淆的字符串原文
     * @since 1.0 <2015-7-9> SoChishun Added.
     */
    public function str_demix($str) {
        if (!$str) {
            return $str;
        }
        $dict_idx = 'hUBQDoaSmipMGgtfqvFZsCJreVKxLPAjTbkOWcnwYdzXyRlNIuEH';
        $dict_org = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+=/-';
        $idx = $str[0];
        $id = strpos($dict_idx, $idx);
        $str = substr($str, 1);
        $dictmix = $this->str_dict[$id];
        $n = strlen($str) - 1;
        $org = '';
        while ($n >= 0) {
            $i = strpos($dictmix, $str[$n]);
            $org.=$dict_org[$i];
            $n--;
        }
        return $org;
    }

    /**
     * 字符串序列化
     * @param string $str 字符串
     * @param int $count 序列组数量
     * @param char $splitor 序列分隔符
     * @return string
     * @since 1.0 <2015-7-9> SoChishun Added.
     */
    public function str_serial($str, $count, $splitor = '-') {
        $len = strlen($str);
        $n = ceil($len / $count);
        return wordwrap($str, $n, $splitor, true);
    }

    /**
     * 字符串混淆加密并串行化
     * 用于生成授权序列号,支持中文、英文、数值、符号
     * @param string|array $data 原文
     * @param string $key 混淆密匙
     * @param int $count 序列号组数量
     * @param char $splitor 序列号分隔符
     * @return string 混淆加密后的密文,密文可能包含符号(%#*.)
     * @since 1.0 <2015-7-9> SoChishun Added.
     * @example str_encserial(array('status' => true, 'co.' => '鑫优祥.lnc', 'sn' => 'pwd!@#$%^&*_+<?[}密匙', 'url' => 'http://www.abc.com', 'date' => '2015-7-9 13:23:30 星期四', 'author' => '天澜<14507247@qq.com>')) => tYYgn64sEZR9SfQhkZJBkRa2UyOsz%%hxdW6c9k#9m*ce70mg*16JzKNo-WwnZ5zCfRqmbN04qXAIf.Ib6nAEfHg3Wa#uQJRHioC0z%ARW8#yAAXjo7-M4SK#IpnVc7Lh4wkeVXC.sFoqmI.kmkQh%9xzNtB#nHH6coKBRuqdc1NQ-iq1n29vR3LZiHOcd5wKcDyLDSav9eLJzXmje4XzdkZhGPsIv7mpcHVph9-Vv%HXt4HoFAFUQJAzsbQVLkp%..euSzxeXqGSVNeIExADWA#hmVp%
     */
    public function str_encserial($data, $key = 'Su!!!', $count = 5, $splitor = '-') {
        $str = is_array($data) ? json_encode($data) : $data;
        $str = $this->des($str, $key); // Des加密
        $mix = $this->str_mix($str); // 混淆
        if ($splitor) {
            $mix = $this->str_serial($mix, $count, $splitor); // 串列
        }
        return $mix;
    }

    /**
     * 字符串反混淆加密串行化
     * @param string $str 密文
     * @param string $key 混淆密匙
     * @param char $splitor 序列号分隔符
     * @param boolean $parse_array 是否转换为Array
     * @return string|Array
     * @since 1.0 <2015-7-9> SoChishun Added.
     * @example str_dencserial('tYYgn64sEZR9SfQhkZJBkRa2UyOsz%%hxdW6c9k#9m*ce70mg*16JzKNo-WwnZ5zCfRqmbN04qXAIf.Ib6nAEfHg3Wa#uQJRHioC0z%ARW8#yAAXjo7-M4SK#IpnVc7Lh4wkeVXC.sFoqmI.kmkQh%9xzNtB#nHH6coKBRuqdc1NQ-iq1n29vR3LZiHOcd5wKcDyLDSav9eLJzXmje4XzdkZhGPsIv7mpcHVph9-Vv%HXt4HoFAFUQJAzsbQVLkp%..euSzxeXqGSVNeIExADWA#hmVp%') => {"status":true,"co.":"\u946b\u4f18\u7965.lnc.co","sn":"pwd!@#$%^&*()_+<>?{}[]\u5bc6\u5319","url":"http:\/\/www.abc.com","date":"2015-7-9 13:23:30 \u661f\u671f\u56db","author":"\u5929\u6f9c<14507247@qq.com>"}
     */
    public function str_dencserial($str, $key = 'Su!!!', $splitor = '-', $parse_array = false) {
        $str = str_replace('-', '', $str); // 反串列
        $org = $this->str_demix($str); // 反混淆
        $str = $this->dedes($org, $key); // 反Des加密
        return $parse_array ? (Array) json_decode($str) : $str;
    }

    // ==/字符串加密==
    // ==密码加密==
    /**
     * 加密密码
     * @param string $str 原密码
     * @param string $key 混淆密匙
     * @param string $flag 密码标识,建议加上,以兼容未加密的密码
     * @return string 加密后的密码,长度不超过原文长度的2倍(密码越长,增量比例越少),密文可能包含符号(%#*.)
     * @since 1.0 <2015-7-9> SoChishun Added.
     * @example password('root!@#xcall098)(*Admin%^&') => '*p2y3U6nn87DUNmX7XltpCnh95ZajtiPflhUTxJrVbVGTk'
     */
    public function password($str, $key = 'Su!!!', $flag = '*') {
        if (!$str) {
            return $str;
        }
        $str = $this->des($str, $key); // Des加密
        return $flag . $this->str_mix($str); // 混淆
    }

    /**
     * 解密密码
     * @param string $str 加密后的密码
     * @param string $key 混淆密匙
     * @param string $flag 密码标识
     * @return string 密码原文
     * @since 1.0 <2015-7-9> SoChishun Added.
     * @example depassword('*p2y3U6nn87DUNmX7XltpCnh95ZajtiPflhUTxJrVbVGTk') => root!@#xcall098)(*Admin%^&
     */
    public function depassword($str, $key = 'Su!!!', $flag = '*') {
        if (!$str || ($flag && $flag !== $str[0])) {
            return $str;
        }
        if ($flag) {
            $str = substr($str, strlen($flag));
        }
        $org = $this->str_demix($str); // 反混淆
        return $this->dedes($org, $key); // 反Des加密
    }

    // ==/密码加密==
    // ==数字加密==
    protected static $number_dict = array(
        '123456789',
        '0872953164',
        '5346829017',
        '8420157396',
        '9652473180',
        '1408792536',
        '8270315964',
        '6910578423',
        '8415920673',
        '0275819463',
        '5863724019',
    );

    /**
     * 混淆数值
     * <br />支持浮点、负数等
     * @param number $num 浮点数、负数等各种数值
     * @return string 返回无符号整数值,比原长度多2位
     * @since 1.0 <2015-7-9> SoChishun Added.
     * @example number_mix(-1699123.0123) => 50416041339121
     */
    public static function number_mix($num) {
        if (!$num || !is_numeric($num)) {
            return $num;
        }
        $str = strval($num);
        $prefix = '0';
        switch ($str[0]) {
            case '+':
                $str = substr($str, 1);
                break;
            case '-':
                $prefix = '1';
                $str = substr($str, 1);
                break;
        }
        if (false !== ($idot = strrpos($str, '.'))) {
            $prefix.=$idot;
            $str = str_replace('.', '', $str);
        } else {
            $prefix.='0';
        }
        $str = $prefix . $str; // [id][symo_flag][float_flag]
        $id = rand(1, 9);
        $dict_org = '1234567890';
        $dictmix = self::$number_dict[$id];
        $n = strlen($str) - 1;
        $out = '';
        while ($n >= 0) {
            $i = strpos($dict_org, $str[$n]);
            $out.=$dictmix[$i];
            $n--;
        }
        return $id . $out;
    }

    /**
     * 数值反混淆
     * @param number $num 整数值
     * @return string
     * @since 1.0 <2015-7-9> SoChishun Added.
     * @example number_demix(50416041339121) => -1699123.0123
     */
    public static function number_demix($num) {
        if (!$num || !ctype_digit($num)) {
            return $num;
        }
        $str = strval($num);
        $id = $str[0];
        $dict_org = '1234567890';
        $dictmix = self::$number_dict[$id];
        $str = substr($str, 1);
        $n = strlen($str) - 1;
        $out = '';
        while ($n >= 0) {
            $i = strpos($dictmix, $str[$n]);
            $out.=$dict_org[$i];
            $n--;
        }
        $symbol = $out[0];
        $idot = $out[1];
        $str = substr($out, 2);
        if ('0' != $idot) {
            $str = substr_replace($str, '.', $idot, 0);
        }
        if ('1' == $symbol) {
            $str = '-' . $str;
        }
        return $str;
    }

    // ==/数字加密==
    // ==DES==
    /**
     * 加密
     * @param string $str 要处理的字符串
     * @param string $key 密钥,必须为8位
     * @return string 字符串可能包含+=/字符
     */
    public function des($str, $key = 'So123!@#') {
        $size = mcrypt_get_block_size(MCRYPT_DES, MCRYPT_MODE_CBC);
        $str = $this->pkcs5Pad($str, $size);
        $aaa = mcrypt_cbc(MCRYPT_DES, $key, $str, MCRYPT_ENCRYPT, $key);
        $ret = base64_encode($aaa);
        return $ret;
    }

    /**
     * 解密
     * @param string $str 要处理的字符串
     * @param string $key 密钥,必须为8位
     * @return string
     */
    public function dedes($str, $key = 'So123!@#') {
        $strBin = base64_decode($str);
        $str = mcrypt_cbc(MCRYPT_DES, $key, $strBin, MCRYPT_DECRYPT, $key);
        $str = $this->pkcs5Unpad($str);
        return $str;
    }

    function hex2bin($hexData) {
        $binData = "";
        for ($i = 0; $i < strlen($hexData); $i += 2) {
            $binData .= chr(hexdec(substr($hexData, $i, 2)));
        }
        return $binData;
    }

    function pkcs5Pad($text, $blocksize) {
        $pad = $blocksize - (strlen($text) % $blocksize);
        return $text . str_repeat(chr($pad), $pad);
    }

    function pkcs5Unpad($text) {
        $pad = ord($text {strlen($text) - 1});
        if ($pad > strlen($text))
            return false;

        if (strspn($text, chr($pad), strlen($text) - $pad) != $pad)
            return false;

        return substr($text, 0, - 1 * $pad);
    }

    // ==/DES==
    // ==其他加密算法==

    /**
     * 字符串加密解密算法
     * 加密后变成乱码,可还原
     * @param string $data 字符串
     * @param string $mode 模式,ENCRYPT=加密,DENCRYPT=解密
     * @param string $key 密匙
     * @return string 返回加密后的密文或还原后的原文
     * @since 1.0 <2014-10-15> SoChishun Added.
     * @example
     *  $str = encrypt_file('hello sutroon ! 好厉害！'); // Jyl�n�ah�-Sl�������������yP�
     *  echo $str . '<br />';
     *  echo encrypt_file($str, 'DENCRYPT'); // hello sutroon ! 好厉害！
     */
    public function encrypt_file($data, $mode = 'ENCRYPT', $key = 'sutroon!@#') {
        $td = mcrypt_module_open(MCRYPT_DES, '', 'ecb', ''); //使用MCRYPT_DES算法,ecb模式
        $iv = mcrypt_create_iv(mcrypt_enc_get_iv_size($td), MCRYPT_RAND);
        $ks = mcrypt_enc_get_key_size($td);
        $key = substr(md5($key), 0, $ks);
        mcrypt_generic_init($td, $key, $iv); //初始处理
        switch ($mode) {
            case 'ENCRYPT':
                $data = mcrypt_generic($td, $data); //加密
                break;
            case 'DENCRYPT':
                $data = trim(mdecrypt_generic($td, $data)) . "\n"; //解密后,可能会有后续的\0,需去掉
                break;
        }
        //结束
        mcrypt_generic_deinit($td);
        mcrypt_module_close($td);
        return $data;
    }

    /**
     * 随机加密算法
     * @param string $data
     * @param string $mode
     * @param string $key
     * @return string
     * @since 1.0 <2014-10-15> SoChishun Added.
     * @example
     *  $encode = encrypt_rnd("测试内容abc123!@#", 'ENCRYPT', '123');
     *  echo "加密结果：" . $encode; // CewAtwqAAulV+AufAuQChlDUAedQ/FfvVzVQMFEzBzUAMAMxV3UDQQUn,随机变化
     *  $decode = encrypt_rnd($encode, 'DENCRYPT', '123');
     *  echo "<br />解密结果：" . $decode; // 测试内容abc123!@#
     */
    public function encrypt_rnd($data, $mode = 'ENCRYPT', $key = 'sutroon!@#') {
        if ($mode == 'ENCRYPT') {
            srand((double) microtime() * 1000000);
            $rand = rand(1, 10000);
            $encrypt_key = md5($rand);
            $ctr = 0;
            $tmp = '';
            for ($i = 0; $i < strlen($data); $i++) {
                $ctr = $ctr == strlen($encrypt_key) ? 0 : $ctr;
                $tmp .= $encrypt_key[$ctr] . ($data[$i] ^ $encrypt_key[$ctr++]);
            }
            $tmp = $this->encrypt_rnd_key($tmp, $key);
            return base64_encode($tmp);
        }
        if ($mode == 'DENCRYPT') {
            $txt = base64_decode($data);
            $txt = $this->encrypt_rnd_key($txt, $key);
            $tmp = '';
            for ($i = 0; $i < strlen($txt); $i++) {
                $md5 = $txt[$i];
                $tmp .= $txt[++$i] ^ $md5;
            }
            return $tmp;
        }
        return $data;
    }

    /**
     * encrypt_rnd()辅助函数
     * @param $data 字符串
     * @param $encrypt_key 密匙
     * @return string 加密后的字符串
     * @since 1.0 <2014-10-15> SoChishun Added.
     */
    private function encrypt_rnd_key($data, $key) {
        $ctr = 0;
        $tmp = '';
        for ($i = 0; $i < strlen($data); $i++) {
            $ctr = $ctr == strlen($key) ? 0 : $ctr;
            $tmp .= $data[$i] ^ $key[$ctr++];
        }
        return $tmp;
    }

    // ==/其他加密算法==
}
